﻿
// Генератор технической документации по парсеру

Перем Узлы;
Перем Токены;
Перем Типы;
Перем Директивы;
Перем Аннотации;
Перем СимволыПрепроцессора;
Перем ТаблицаТокенов;
Перем Исходник;

Перем Результат;

Функция Посетить(Парсер, Параметры) Экспорт
	
	Узлы = Парсер.Узлы();
	Токены = Парсер.Токены();
	Типы = Парсер.Типы();
	Директивы = Парсер.Директивы();
	Аннотации = Парсер.Аннотации();
	СимволыПрепроцессора = Парсер.СимволыПрепроцессора();
	ТаблицаТокенов = Парсер.ТаблицаТокенов();
	Исходник = Парсер.Исходник();
	
	Результат = Новый Массив;
	
	Результат.Добавить(
		"<!DOCTYPE html>
		|<html>
		|<head>
		|<meta http-equiv='Content-Тип' content='text/html; charset=utf-8'>
		|<title>Парсер встроенного языка</title>
		|<link rel='stylesheet' type='text/css' href='ast.css'>
		|</head>
		|<body>
		|<header>
		|<h1>Парсер встроенного языка</h1>
		|</header>
		|На данной странице находится быстрая справка.<br>
		|Руководство по парсеру находится в <a href='https://github.com/oscript-library/osparser/blob/master/book.md'>Книге Джедая</a>.<br>
		|<br>
		|Настоящим Джедаям рекомендуется смотреть исходный код.<br>
		|Только так можно достичь просветления.
		|<h2>Содержание</h2>
		|<ul>
		|<li><a href='#Примеры'>Примеры использования парсера</a></li>
		|<li><a href='#МинимальныйШаблон'>Минимальный шаблон плагина</a></li>
		|<li><a href='#ПолныйШаблон'>Полный шаблон плагина</a></li>
		|<li><a href='#Таблицы'>Таблицы</a></li>
		|	<ul>
		|	<li><a href='#ТаблицаТокенов'>ТаблицаТокенов</a></li>
		|	<li><a href='#ТаблицаОшибок'>ТаблицаОшибок</a></li>
		|	<li><a href='#ТаблицаЗамен'>ТаблицаЗамен</a></li>
		|	</ul>
		|<li><a href='#АСД'>Абстрактное синтаксическое дерево</a></li>
		|	<ul>
		|	<li><a href='#Объявления'>Объявления</a></li>
		|	<li><a href='#Выражения'>Выражения</a></li>
		|	<li><a href='#Операторы'>Операторы</a></li>
		|	<li><a href='#ИнструкцииПрепроцессора'>ИнструкцииПрепроцессора</a></li>
		|	<li><a href='#ВыраженияПрепроцессора'>ВыраженияПрепроцессора</a></li>
		|	<li><a href='#Прочее'>Прочее</a></li>
		|	</ul>
		|<li><a href='#Перечисления'>Перечисления</a></li>
		|	<ul>
		|	<li><a href='#Типы'>Типы</a></li>
		|	<li><a href='#Директивы'>Директивы</a></li>
		|	<li><a href='#Аннотации'>Аннотации</a></li>
		|	<li><a href='#СимволыПрепроцессора'>СимволыПрепроцессора</a></li>
		|	<li><a href='#Токены'>Токены</a></li>
		|	</ul>
		|</ul>
		|<h1 id='Примеры'>Примеры использования парсера</h1><br>
		|<pre>
		|
		|#Использовать osparser
		|#Использовать ""./plugins""
		|
		|ЧтениеТекста = Новый ЧтениеТекста(""Модуль.bsl"");
		|Исходник = ЧтениеТекста.Прочитать();
		|
		|Парсер = Новый ПарсерВстроенногоЯзыка;
		|Плагин = Новый ДетекторНеиспользуемыхПеременных;
		|Парсер.Пуск(Исходник, Плагин);
		|
		|Отчет = Новый Массив;
		|Для Каждого Ошибка Из Парсер.ТаблицаОшибок() Цикл
		|	Отчет.Добавить(Ошибка.Текст);
		|	Отчет.Добавить(СтрШаблон("" [стр: %1; кол: %2]"", Ошибка.НомерСтрокиНачала, Ошибка.НомерКолонкиНачала));
		|	Отчет.Добавить(Символы.ПС);
		|КонецЦикла;
		|Сообщить(СтрСоединить(Отчет));
		|
		|</pre>
		|<h1 id='МинимальныйШаблон'>Минимальный шаблон плагина</h1>
		|<pre>
		|
		|Перем Результат;
		|
		|// Будет вызвана один раз перед обходом AST.
		|// Тут можно получить необходимые перечисления и таблицы из парсера,
		|// и выполнить инициализацию плагина с учетом полученных параметров.
		|Процедура Открыть(Парсер, Параметры) Экспорт
		|	Результат = Новый Массив;
		|КонецПроцедуры
		|
		|// Будет вызвана после полного обхода AST.
		|// Возвращает текстовый результат работы плагина, если он есть.
		|// Плагины, которые регистрируют ошибки и/или замены, могут вернуть Неопределено.
		|Функция Закрыть() Экспорт
		|	Возврат СтрСоединить(Результат);
		|КонецФункции
		|
		|// Возвращает список процедур-подписок, которые будут вызываться визитером.
		|// Состав возможных подписок можно посмотреть в исходнике парсера в функции Подписки().
		|// Имена большинства подписок образуются добавлением префикса Посетить/Покинуть к имени типа узла.
		|// Справка по типам узлов находится по этому адресу: https://oscript-library.github.io/osparser/
		|Функция Подписки() Экспорт
		|	Перем Подписки;
		|	Подписки = Новый Массив;
		|	Подписки.Добавить(""ПосетитьОператорПрисваивания"");
		|	Подписки.Добавить(""ПокинутьОператорПрисваивания"");
		|	Возврат Подписки;
		|КонецФункции
		|
		|#Область РеализацияПодписок
		|
		|// Описание структуры узла `ОператорПрисваивания`: https://oscript-library.github.io/osparser/#ОператорПрисваивания
		|
		|// Данная процедура будет вызвана при посещении узла AST перед посещением подчиненных ему узлов.
		|// Вызов подписок с префиксом `Посетить` отражает рекурсивный спуск визитера по AST.
		|// Сначала вызывается подписка на родительский узел, потом на этот, потом на подчиненный и так далее.
		|Процедура ПосетитьОператорПрисваивания(ОператорПрисваивания) Экспорт
		|	// ...
		|КонецПроцедуры
		|
		|// Данная процедура будет вызвана при посещении узла AST после посещения подчиненных ему узлов.
		|// Вызов подписок с префиксом `Покинуть` отражает рекурсивный подъем визитера по AST.
		|// Сначала вызывается подписка на подчиненный узел, потом на этот, потом на родительский и так далее.
		|Процедура ПокинутьОператорПрисваивания(ОператорПрисваивания) Экспорт
		|	// ...
		|КонецПроцедуры
		|
		|#КонецОбласти
		|
		|</pre>
		|
		|</pre>
		|<h1 id='ПолныйШаблон'>Полный шаблон плагина</h1>
		|<pre>
		|Перем Типы;
		|Перем Токены;
		|Перем Исходник;
		|Перем ТаблицаТокенов;
		|Перем ТаблицаОшибок;
		|Перем ТаблицаЗамен;
		|Перем Стек;
		|Перем Счетчики;
		|Перем Директивы;
		|Перем Аннотации;
		|Перем СимволыПрепроцессора;
		|
		|Перем Результат;
		|
		|Процедура Открыть(Парсер, Параметры) Экспорт
		|	
		|	Типы = Парсер.Типы();
		|	Токены = Парсер.Токены();
		|	Исходник = Парсер.Исходник();
		|	ТаблицаТокенов = Парсер.ТаблицаТокенов();
		|	ТаблицаОшибок = Парсер.ТаблицаОшибок();
		|	ТаблицаЗамен = Парсер.ТаблицаЗамен();
		|	Стек = Парсер.Стек();
		|	Счетчики = Парсер.Счетчики();
		|	Директивы = Парсер.Директивы();
		|	Аннотации = Парсер.Аннотации();
		|	СимволыПрепроцессора = Парсер.СимволыПрепроцессора();
		|	
		|	Результат = Новый Массив;
		|	
		|КонецПроцедуры
		|
		|Функция Закрыть() Экспорт
		|	// ...
		|	Возврат СтрСоединить(Результат);
		|КонецФункции
		|
		|Функция Подписки() Экспорт
		|	Перем Подписки;
		|	Подписки = Новый Массив;
		|	Подписки.Добавить(""ПосетитьОператорПрисваивания"");
		|	//Подписки.Добавить(""ПокинутьОператорПрисваивания"");
		|	Возврат Подписки;
		|КонецФункции
		|
		|#Область РеализацияПодписок
		|
		|Процедура ПосетитьОператорПрисваивания(ОператорПрисваивания) Экспорт
		|	Ошибка(""Ошибка в операторе присваивания"", ОператорПрисваивания.Начало, ОператорПрисваивания.Конец);
		|КонецПроцедуры // ПосетитьОператорПрисваивания()
		|
		|//Процедура ПокинутьОператорПрисваивания(ОператорПрисваивания) Экспорт
		|//
		|//КонецПроцедуры // ПокинутьОператорПрисваивания()
		|
		|#КонецОбласти
		|
		|Процедура Ошибка(Текст, Начало, Конец = Неопределено, ЕстьЗамена = Ложь)
		|	Ошибка = ТаблицаОшибок.Добавить();
		|	Ошибка.Источник = ""ИмяЭтогоПлагина"";
		|	Ошибка.Текст = Текст;
		|	Ошибка.ПозицияНачала = Начало.Позиция;
		|	Ошибка.НомерСтрокиНачала = Начало.НомерСтроки;
		|	Ошибка.НомерКолонкиНачала = Начало.НомерКолонки;
		|	Если Конец = Неопределено Или Конец = Начало Тогда
		|		Ошибка.ПозицияКонца = Начало.Позиция + Начало.Длина;
		|		Ошибка.НомерСтрокиКонца = Начало.НомерСтроки;
		|		Ошибка.НомерКолонкиКонца = Начало.НомерКолонки + Начало.Длина;
		|	Иначе
		|		Ошибка.ПозицияКонца = Конец.Позиция + Конец.Длина;
		|		Ошибка.НомерСтрокиКонца = Конец.НомерСтроки;
		|		Ошибка.НомерКолонкиКонца = Конец.НомерКолонки + Конец.Длина;
		|	КонецЕсли;
		|	Ошибка.ЕстьЗамена = ЕстьЗамена;
		|КонецПроцедуры
		|
		|Процедура Замена(Текст, Начало, Конец = Неопределено)
		|	НоваяЗамена = ТаблицаЗамен.Добавить();
		|	НоваяЗамена.Источник = ""ИмяЭтогоПлагина"";
		|	НоваяЗамена.Текст = Текст;
		|	НоваяЗамена.Позиция = Начало.Позиция;
		|	Если Конец = Неопределено Тогда
		|		НоваяЗамена.Длина = Начало.Длина;
		|	Иначе
		|		НоваяЗамена.Длина = Конец.Позиция + Конец.Длина - Начало.Позиция;
		|	КонецЕсли;
		|КонецПроцедуры
		|
		|Процедура Вставка(Текст, Позиция)
		|	НоваяЗамена = ТаблицаЗамен.Добавить();
		|	НоваяЗамена.Источник = ""ИмяЭтогоПлагина"";
		|	НоваяЗамена.Текст = Текст;
		|	НоваяЗамена.Позиция = Позиция;
		|	НоваяЗамена.Длина = 0;
		|КонецПроцедуры
		|
		|</pre>
		|	<h1 id='Таблицы'>Таблицы</h1>
		|	<h3 id='ТаблицаТокенов'>ТаблицаТокенов<a class='permalink' href='#ТаблицаТокенов'>¶</a></h3>
		|	<ul>
		|	    <p>
		|		<i> Таблица значений, которая хранит подробную информацию о всех токенах. </i><br>
		|		<i> Каждый узел AST имеет поля Начало и Конец, которые содержат строки из этой таблицы. </i><br>
		|		<i> С помощью обращения к таблице по индексу можно получить доступ к любому токену. </i><br>
		|		<i> Комментарии при необходимости тоже извлекаются по данным этой таблицы. </i><br>
		|		<i> Таблицу можно получить вызовом Парсер.ТаблицаТокенов() </i><br>
		|		</p>
		|	    <li><strong>Индекс</strong>: число</li>
		|		<li><strong>Токен</strong>: строка (один из <a href='#Токены'>#Токены</a>)</li>
		|		<li><strong>НомерСтроки</strong>: число</li>
		|		<li><strong>НомерКолонки</strong>: число</li>
		|		<li><strong>Позиция</strong>: число</li>
		|		<li><strong>Длина</strong>: число</li>
		|	</ul>
		|
		|	<h3 id='ТаблицаОшибок'>ТаблицаОшибок<a class='permalink' href='#ТаблицаОшибок'>¶</a></h3>
		|	<ul>
		|	    <p>
		|		<i> Таблица значений, которая хранит ошибки собранные парсером и плагинами. </i><br>
		|		<i> Плагины могут получить таблицу в методе Открыть() и регистрировать в ней ошибки в процессе своей работы. </i><br>
		|		<i> Таблицу можно получить вызовом Парсер.ТаблицаОшибок() </i><br>
		|		<i> Обязательны к заполнению только первые 4 поля. Поле Код плагины не должны заполнять. </i><br>
		|		<i> Если плагин регистрирует ошибку и замену ее исправляющую, то желательно установить ЕстьЗамена = Истина </i><br>
		|		</p>
		|	    <li><strong>Источник</strong>: строка</li>
		|		<li><strong>Текст</strong>: строка</li>
		|		<li><strong>ПозицияНачала</strong>: число</li>
		|		<li><strong>ПозицияКонца</strong>: число</li>
		|		<li><strong>ЕстьЗамена</strong>: булево</li>
		|		<li><strong>Код</strong>: число</li>
		|		<li><strong>НомерСтрокиНачала</strong>: число</li>
		|		<li><strong>НомерКолонкиНачала</strong>: число</li>
		|		<li><strong>НомерСтрокиКонца</strong>: число</li>
		|		<li><strong>НомерКолонкиКонца</strong>: число</li>
		|		<li><strong>МинутНаИсправление</strong>: число</li>
		|		<li><strong>Серьезность</strong>: строка</li>
		|		<li><strong>Приоритет</strong>: число</li>
		|		<li><strong>Правило</strong>: строка</li>
		|		<li><strong>Тип</strong>: строка</li>
		|	</ul>
		|
		|	<h3 id='ТаблицаЗамен'>ТаблицаЗамен<a class='permalink' href='#ТаблицаЗамен'>¶</a></h3>
		|	<ul>
		|	    <p>
		|		<i> Таблица значений, которая хранит замены, регистрируемые плагинами. </i><br>
		|		<i> Замена - это операция, аналогичная выделению участка текста и вставки фрагмента из буфера обмена. </i><br>
		|		<i> Позиция и Длина указывают что выделить, а Текст - это вставляемый фрагмент. </i><br>
		|		<i> Плагины могут получить таблицу в методе Открыть() и регистрировать в ней замены фрагментов исходника. </i><br>
		|		<i> Фактические операции замены по таблице могут быть выполнены вызовом Парсер.ВыполнитьЗамены() </i><br>
		|		<i> Таблицу можно получить вызовом Парсер.ТаблицаЗамен() </i><br>
		|		</p>
		|	    <li><strong>Источник</strong>: число</li>
		|		<li><strong>Текст</strong>: строка</li>
		|		<li><strong>Позиция</strong>: число</li>
		|		<li><strong>Длина</strong>: число</li>
		|	</ul>"
		""
	);
	
	Имена = Новый Структура;
	Имена.Вставить("ИнициализироватьУзлы", Ложь);
	Имена.Вставить("ИнициализироватьОбъявления", Истина);
	Имена.Вставить("ИнициализироватьВыражения", Истина);
	Имена.Вставить("ИнициализироватьОператоры", Истина);
	Имена.Вставить("ИнициализироватьИнструкцииПрепроцессора", Истина);
	Имена.Вставить("ИнициализироватьВыраженияПрепроцессора", Истина);
	
	Результат.Добавить("	<h1 id='АСД'>Абстрактное синтаксическое дерево</h1>" "");
	
	Для Каждого Объявление Из Узлы.ОбъявлениеМетода Цикл
		
		ДобавлятьЗаголовок = Ложь;
		
		Если Имена.Свойство(Объявление.Сигнатура.Имя, ДобавлятьЗаголовок) Тогда
			
			Если ДобавлятьЗаголовок Тогда
				Результат.Добавить(СтрШаблон("	<h2 id='%1'>%1</h2>" "", Сред(Объявление.Сигнатура.Имя, 17)));
			КонецЕсли; 
			
			Для Каждого Оператор Из Объявление.Операторы Цикл
				
				Если Оператор.Тип = Типы.ОператорВызоваПроцедуры Тогда
					
					Если Оператор.Идентификатор.Голова.Имя = "Парсер_Узлы" Тогда
												
						УзелИмя = Оператор.Идентификатор.Хвост[0].Аргументы[0].Элементы[0].Значение;
						
						СписокОписаний = Новый СписокЗначений;
						
						ДанныеСледующегоТокена = ТаблицаТокенов[Оператор.Конец.Индекс + 2];
						Пока ДанныеСледующегоТокена.Токен = Токены.Комментарий Цикл
							СписокОписаний.Добавить(Сред(Исходник, ДанныеСледующегоТокена.Позиция, ДанныеСледующегоТокена.Длина));
							ДанныеСледующегоТокена = ТаблицаТокенов[ДанныеСледующегоТокена.Индекс + 1];
						КонецЦикла; 
						
						Результат.Добавить(СтрШаблон(
							"	<h3 id='%1'>%1<a class='permalink' href='#%1'>¶</a></h3>
							|	<ul>" "",
							УзелИмя
						));
						
						Результат.Добавить("		<p>" "");
						
						КоличествоОписаний = СписокОписаний.Количество();
						Индекс = 0;
						Пока Индекс < КоличествоОписаний Цикл
							Элемент = СписокОписаний[Индекс];
							Если СокрЛП(Элемент.Значение) = "<pre>" Тогда
								Буфер = Новый Массив;
								Пока СокрЛП(Элемент.Значение) <> "</pre>" Цикл
									Буфер.Добавить(Элемент.Значение);
									Индекс = Индекс + 1;
									Элемент = СписокОписаний[Индекс];
								КонецЦикла;
								Результат.Добавить(СтрСоединить(Буфер, Символы.ПС));
								Результат.Добавить("</pre>");
								Результат.Добавить(Символы.ПС);
							Иначе
								Результат.Добавить(СтрШаблон("		<i>%1</i><br>" "", Элемент.Значение));
							КонецЕсли;
							Индекс = Индекс + 1;
						КонецЦикла;
						
						Результат.Добавить("		</p>" "");
						
					ИначеЕсли Оператор.Идентификатор.Голова.Имя = "Поля" Тогда	
						
						ИмяПоля = Оператор.Идентификатор.Хвост[0].Аргументы[0].Элементы[0].Значение;
												
						Если Прав(ИмяПоля, 1) = "," Тогда
							ИмяПоля = Лев(ИмяПоля, СтрДлина(ИмяПоля) - 1);
						КонецЕсли;
						
						ДанныеСледующегоТокена = ТаблицаТокенов[Оператор.Конец.Индекс + 2];
						СписокТипов = РазобратьТипы(Сред(Исходник, ДанныеСледующегоТокена.Позиция, ДанныеСледующегоТокена.Длина));
						
						Результат.Добавить(СтрШаблон(
							"		<li><strong>%1</strong>: %2%3</li>" "",
							ИмяПоля,
							СгенерироватьСписокТипов(СписокТипов),
							?(ИмяПоля = "Тип", " = Типы." + УзелИмя, "")
						));
						
						Если ИмяПоля = "Конец" Или ИмяПоля = "Доступность" Или ИмяПоля = "Интерфейс" Или ИмяПоля = "Объявление" Тогда
							Результат.Добавить("	</ul>" "");
						КонецЕсли; 
						
					КонецЕсли; 
					
				КонецЕсли; 
				
			КонецЦикла; 
			
		КонецЕсли; 
		
	КонецЦикла; 
		
	Результат.Добавить(
		"<h2 id='Прочее'>Прочее</h2>
		|<h3 id='Доступность'>Доступность</h3>
		|<ul>
		| <li><strong>Клиент</strong>: булево</li>
		| <li><strong>ВнешнееСоединение</strong>: булево</li>
		| <li><strong>МобильноеПриложение</strong>: булево</li>
		| <li><strong>МобильныйКлиент</strong>: булево</li>
		| <li><strong>МобильныйСервер</strong>: булево</li>
		| <li><strong>Сервер</strong>: булево</li>
		| <li><strong>ТолстыйКлиент</strong>: булево</li>
		| <li><strong>ТонкийКлиент</strong>: булево</li>
		| <li><strong>ВебКлиент</strong>: булево</li>
		| <li><strong>Интеграция</strong>: булево</li>
		|</ul>
		|</body>
		|</html>"
	);
	
	Результат.Добавить("<h1 id='Перечисления'>Перечисления</h1>");
	Результат.Добавить(СгенерироватьПеречисление("Типы", Типы, Истина));
	Результат.Добавить(СгенерироватьПеречисление("Директивы", Директивы));
	Результат.Добавить(СгенерироватьПеречисление("Аннотации", Аннотации));
	Результат.Добавить(СгенерироватьПеречисление("СимволыПрепроцессора", СимволыПрепроцессора));
	Результат.Добавить(СгенерироватьПеречисление("Токены", Токены));
	
	Возврат СтрСоединить(Результат);
	
КонецФункции 

Функция СгенерироватьПеречисление(Имя, Перечисление, Ссылка = Ложь)
	Перем Буфер;
	Буфер = Новый Массив;
	Буфер.Добавить(СтрШаблон(
		"<h2 id='%1'>%1</h2>
		|<ul>",
		Имя
	));
	ЗначенияПеречисления = Новый Структура;
	Для Каждого Элемент Из Перечисление Цикл
		ЗначенияПеречисления.Вставить(Элемент.Значение);
	КонецЦикла;
	Для Каждого Элемент Из ЗначенияПеречисления Цикл
		Если Ссылка Тогда
			Буфер.Добавить(СтрШаблон("<li><a href='#%1'>%1</a></li>" "", Элемент.Key));
		Иначе
			Буфер.Добавить(СтрШаблон("<li>%1</li>" "", Элемент.Key));
		КонецЕсли;
	КонецЦикла;
	Буфер.Добавить("</ul>" "");
	Возврат СтрСоединить(Буфер);
КонецФункции // СгенерироватьПеречисление()

Функция СгенерироватьСписокТипов(СписокТипов)
	Перем Буфер;
	Буфер = Новый Массив;
	Для Каждого Элемент Из СписокТипов Цикл
		Если Элемент.Ребенок = Неопределено Тогда
			Если Lower(Элемент.Идентификатор) = Элемент.Идентификатор Тогда
				Буфер.Добавить(Элемент.Идентификатор);
			Иначе
				Идентификатор = Элемент.Идентификатор;
				Если СтрНачинаетсяС(Идентификатор, "#") Тогда
					Идентификатор = Сред(Идентификатор, 2);
				КонецЕсли;
				Буфер.Добавить(СтрШаблон(
					"<a href='#%1'>%1</a>",
					Идентификатор
				));
			КонецЕсли;
		ИначеЕсли ТипЗнч(Элемент.Ребенок) = Тип("Строка") Тогда
			Ребенок = Элемент.Ребенок;
			Если СтрНачинаетсяС(Ребенок, "#") Тогда
				Ребенок = Сред(Ребенок, 2);
			КонецЕсли;
			Буфер.Добавить(СтрШаблон(
				"%1 <a href='#%2'>%2</a>",
				Элемент.Идентификатор,
				Ребенок
			));
		Иначе
			Буфер.Добавить(СтрШаблон(
				"%1 (%2)",
				Элемент.Идентификатор,
				СгенерироватьСписокТипов(Элемент.Ребенок)
			));
		КонецЕсли;
	КонецЦикла;
	Возврат СтрСоединить(Буфер, ", ");
КонецФункции

#Область ПарсерТипов

Функция РазобратьТипы(ТипыСтрокой)
	Перем Позиция, Идентификатор, Список;
	Позиция = 1; Список = Новый Массив;
	Пока Истина Цикл
		Ребенок = Неопределено;
		ПропуститьНевидимыеСимволы(ТипыСтрокой, Позиция);
		Идентификатор = СканироватьИдентификатор(ТипыСтрокой, Позиция);
		ПропуститьНевидимыеСимволы(ТипыСтрокой, Позиция);
		Если Идентификатор = "один" Тогда
			Если Сред(ТипыСтрокой, Позиция, 2) <> "из" Тогда
				ВызватьИсключение "ошибка";
			КонецЕсли;
			Позиция = Позиция + 2;
			Идентификатор = "один из";
			ПропуститьНевидимыеСимволы(ТипыСтрокой, Позиция);
			Ребенок = СканироватьИдентификатор(ТипыСтрокой, Позиция);
		ИначеЕсли Сред(ТипыСтрокой, Позиция, 1) = "(" Тогда
			Позиция = Позиция + 1;
			Начало = Позиция;
			ПропуститьДо(ТипыСтрокой, Позиция, ")");
			Ребенок = РазобратьТипы(Сред(ТипыСтрокой, Начало, Позиция - Начало));
			Позиция = Позиция + 1;
		КонецЕсли;
		Список.Добавить(Новый Структура("Идентификатор, Ребенок", Идентификатор, Ребенок));
		Если Сред(ТипыСтрокой, Позиция, 1) <> "," Тогда
			Прервать;
		КонецЕсли;
		Позиция = Позиция + 1;
	КонецЦикла;
	Возврат Список;
КонецФункции

Процедура ПропуститьНевидимыеСимволы(Строка, Позиция)
	Для Позиция = Позиция По СтрДлина(Строка) Цикл
		Если Не ПустаяСтрока(Сред(Строка, Позиция, 1)) Тогда
			Прервать;
		КонецЕсли;
	КонецЦикла;
КонецПроцедуры

Процедура ПропуститьДо(Строка, Позиция, Символ)
	Для Позиция = Позиция По СтрДлина(Строка) Цикл
		Если Сред(Строка, Позиция, 1) = Символ Тогда
			Прервать;
		КонецЕсли;
	КонецЦикла;
КонецПроцедуры

Функция СканироватьИдентификатор(Строка, Позиция)
	Перем Начало, Символ;
	Начало = Позиция;
	Для Позиция = Позиция По СтрДлина(Строка) Цикл
		Символ = Сред(Строка, Позиция, 1);
		Если ПустаяСтрока(Символ) Или Символ = "," Тогда
			Прервать;
		КонецЕсли;
	КонецЦикла;
	Возврат Сред(Строка, Начало, Позиция - Начало);
КонецФункции

#КонецОбласти // ПарсерТипов